﻿using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace NetConnector.Net.Client
{
	/// <summary>
	/// Client wrapper for TcpClient. Sends data in the form of the DataArguments class
	/// </summary>
	public class Client
	{
        #region Private
		private int _commandport;
		private int _dataport;
		private IPAddress _ip;
		private IPHostEntry _clientip;
		private TcpClient _c_c;
		private TcpClient _c_s;
		private string _command;
		private bool _isconnected;
		private Thread listenT;
		private Thread sendT;
		//need a read buffer size
		private Byte[] b = new Byte[256];
		private string data = null;
		private DataArguments _data;
        #endregion

        #region Public Properties

		/// <summary>
		/// Returns the bound port
		/// </summary>
		public int CommandPort { get { return _commandport; } }

		/// <summary>
		/// Returns the bound IP
		/// </summary>
		public IPAddress IP { get { return _ip; } }

		/// <summary>
		/// Returns True|False if client is Connected|Not Connected
		/// </summary>
		public bool IsConnected { get { return _isconnected; } }

		/// <summary>
		/// String command
		/// </summary>
		public string Command { set { _command = value; } }
        #endregion



		/// <summary>
		/// Creates and Starts a client service on the designated port and targets the specified host computer.
		/// </summary>
		/// <param name="CommandPort">Port to send and recieve commands on.  Must between 1024 and 65535</param>
		/// <param name="DataPort">Port to send and recieve data on</param>
		/// <param name="host">The host computer to which you want to connect</param>
		public Client (int CommandPort, int DataPort, string host)
		{
			_isconnected = false;
			_data = new DataArguments ();
			if (CommandPort < 1024 || CommandPort > 65535) {
				throw new ArgumentOutOfRangeException (
					"command port",
					"Value must be between 1024 and 65535."
				);
			}
			if (DataPort < 1024 || DataPort > 65535) {
				throw new ArgumentOutOfRangeException (
					"data port",
					"Value must be between 1024 and 65535."
				);
			}
			if (host == null) {
				throw new ArgumentNullException ("Host can not be null");
			}
			try {
				_commandport = CommandPort;
				_dataport = DataPort;
				IPHostEntry he = new IPHostEntry ();
				he = Dns.GetHostEntry (host);
				_ip = he.AddressList [0];
				//_ip = ip;
				// make client
				//IPEndPoint eip = new IPEndPoint(_ip, _port);
				//_c_s = new TcpClient();
				_c_c = new TcpClient ();
				//listenT = new Thread(new ThreadStart(hConnection));
				//sendT = new Thread(new ParameterizedThreadStart(hSend));
				_clientip = new IPHostEntry ();
				sendclientip ();

			} catch (SocketException ex) {
				throw new SystemException ("Socket error", ex);
			}
		}
		/// <summary>
		/// Starts the client services
		/// </summary>
		public void Start ()
		{
			if (!_isconnected) {
                
			}
		}

		/// <summary>
		/// Connection handler
		/// </summary>
		/// <exception cref="SystemException">Throws an exception with the InnerException passed from the client</exception>
		private void hConnection ()
		{
			try {
              
				_isconnected = true;
			} catch (SocketException ex) {
				_c_c.Close ();
				_c_s.Close ();
				listenT.Abort ();
				sendT.Abort ();
				throw new SystemException (
					"Tcp Client connection problem.",
					ex.InnerException
				);
			}
		}

		/// <summary>
		/// Handles the Sending of data
		/// </summary>
		/// <param name="a"></param>
		private void hSend (object a)
		{
			//Byte[] send;
			//    using (MemoryStream ms = new MemoryStream())
			//    {
			//        BinaryFormatter bf = new BinaryFormatter();
			//        try { bf.Serialize(ms, data); }
			//        catch { return; }
			//        send = ms.ToArray();
			//}
			try {
				TcpClient serverc = (TcpClient)a;
				NetworkStream serverstream = serverc.GetStream ();
				if (serverstream.CanRead) {
					int bytesread;
					while (true) {
						Byte[] incoming = new byte[1024];
						bytesread = 0;
						try { //getting actual siize of bytes from the stream
							bytesread = serverstream.Read (
								incoming,
								0,
								incoming.Length
							);
							MemoryStream ms = new MemoryStream (incoming);
							BinaryFormatter f = new BinaryFormatter ();
							ms.Write (incoming, 0, bytesread);
							ms.Position = 0;
							_data = (DataArguments)f.Deserialize (ms);
						} catch {
							throw new SystemException ("Recieving data error.");
						}
					}
				}
				// send a response, whcih is the machien IP since the data was recieved correctly
				sendclientip ();
			} catch (Exception) {
				throw new SystemException ("Error getting network stream.");
			}

		}

		//private void announce(string ip)
		//{
		//    if (_isconnected == true)       // check is the server is connected
		//    {
		//        sendT.Start(_c_c); // Start the Capture thread as soon as capture is requested
		//        NetworkStream ns = _c_c.GetStream();  // get the stream from server
		//        if (ns.CanWrite == true)           // check if we can write to server stream
		//        {
		//            ASCIIEncoding encoder = new ASCIIEncoding();
		//            byte[] command = encoder.GetBytes(ip);  // convert the word capture into byte
		//            ns.Write(command, 0, command.Length);       // send it to the server stream
		//            ns.Flush();
		//        }
		//    }
		//}

		private void OnCommand (IAsyncResult a)
		{
			TcpListener li = (TcpListener)a.AsyncState;
			TcpClient cl = li.EndAcceptTcpClient (a);
			if (_isconnected == true) {                            // check is the server is connected
				//captureThread.Abort();                         // Suspend the Capture 
				NetworkStream ns = _c_c.GetStream ();       // get the stream from server
				if (ns.CanWrite) {                         // check if we can write to server stream
					ASCIIEncoding encoder = new ASCIIEncoding ();
					byte[] command = encoder.GetBytes (_command);    // convert the word stop into byte
					ns.Write (command, 0, command.Length);       // send it to the server stream
					ns.Flush ();
				}
			}
		}

		private void OnRecieve (IAsyncResult a)
		{
			TcpListener li = (TcpListener)a.AsyncState;
			TcpClient cl = li.EndAcceptTcpClient (a);
			string command = "";
			if (_isconnected == true) {                            // check is the server is connected
				try {
					//captureThread.Abort();                         // Suspend the Capture 
					NetworkStream ns = _c_c.GetStream ();       // get the stream from server
					if (ns.CanRead) {                         // check if we can write to server stream
						Byte[] incoming = new byte[256];
						int i;
						while ((i = ns.Read(incoming, 0, incoming.Length)) != 0) {
							command = Encoding.ASCII.GetString (incoming, 0, i);

						}
						DataArguments da = new DataArguments ();
						da.DataArgs = command;
						DataChanged (da);
					}                    
				} catch (Exception) {
					throw new SystemException ("Error getting network stream.");
				}
			}
		}

		public ManualResetEvent clientconnected = new ManualResetEvent (false);

		//event fires when data is changed
		/// <summary>
		/// Delegate procedure which will handle the event
		/// </summary>
		/// <param name="o"></param>
		/// <param name="args">The arguments provided in the recieved data</param>
		public delegate void DataChangeHandler (DataArguments args);

		//the event
		public event DataChangeHandler DataChanged;

		/// <summary>
		/// The Data to send to the connected clients
		/// </summary>
		/// <param name="data">Data to send.  Type is Tdata which is a custom data class</param>
		public void senddata ()
		{
			if (_data.DataArgs != null) {
				clientconnected.Reset ();
				_c_c.BeginConnect (
					_ip,
					_dataport,
					new AsyncCallback(sendCallback),
					_c_c
				);
				clientconnected.WaitOne ();
				_c_c.Close ();
			} else {
				throw new ArgumentNullException ("Data", "Data Arguments");
			}
		}

		private void sendclientip ()
		{
			_clientip = Dns.GetHostEntry (Environment.MachineName);
			foreach (IPAddress i in _clientip.AddressList) {
				if (i.AddressFamily != AddressFamily.InterNetworkV6) {
					_data.Data = i.ToString ();
					_data.DataArgs = "ipaddress";
					senddata ();
				}
			}
		}

		public void sendCallback (IAsyncResult a)
		{

			TcpClient client = (TcpClient)a.AsyncState;
			client.EndConnect (a);
			NetworkStream stream = client.GetStream ();
			if (stream.CanWrite) {
				Byte[] send;
				using (MemoryStream ms = new MemoryStream()) {
					BinaryFormatter bf = new BinaryFormatter ();
					try {
						bf.Serialize (ms, _data);
					} catch {
						return;
					}
					send = ms.ToArray ();
				}
				stream.Write (send, 0, send.Length);
				stream.Close ();
			}
			clientconnected.Set ();
		}

		public Boolean writetcp ()
		{
			return true;
		}

	}
}

/*
Add Event handlers for data recieved.  
 */
